#include "esp_camera.h"
#include <WiFi.h>
#include "esp_http_server.h"

// ===========================
// Camera server setup (simplified)
// ===========================


#define CAMERA_MODEL_ESP32S3_EYE  // Has PSRAM

#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5

#define Y2_GPIO_NUM 11
#define Y3_GPIO_NUM 9
#define Y4_GPIO_NUM 8
#define Y5_GPIO_NUM 10
#define Y6_GPIO_NUM 12
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 17
#define Y9_GPIO_NUM 16

#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13


// ===========================
// Motor control pins (双电机)
// ===========================
#define MOTOR_IN1 36
#define MOTOR_IN2 37
#define ENA 35

#define MOTOR_IN11 39
#define MOTOR_IN22 40
#define ENAA 38


const int SPEED = 130;  //速度控制（0-255）

// ===========================
// WiFi credentials
// ===========================
const char *ssid = "ChinaNet-6x8c";
const char *password = "8zeymm8c";
// ===========================
// Function declarations
// ===========================
void startCameraServer();
void motorStop();
void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  Serial.println("===================camera init start===================");
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 9000000;
  config.frame_size = FRAMESIZE_UXGA;
  config.pixel_format = PIXFORMAT_JPEG;  // for streaming
  //config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if (config.pixel_format == PIXFORMAT_JPEG) {
    if (psramFound()) {
      config.jpeg_quality = 30;
      config.fb_count = 2;
      config.grab_mode = CAMERA_GRAB_LATEST;
    } else {
      // Limit the frame size when PSRAM is not available
      config.frame_size = FRAMESIZE_SVGA;
      config.fb_location = CAMERA_FB_IN_DRAM;
    }
  } else {
    // Best option for face detection/recognition
    config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
    config.fb_count = 2;
#endif
  }

#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t *s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1);        // flip it back
    s->set_brightness(s, 1);   // up the brightness just a bit
    s->set_saturation(s, -2);  // lower the saturation
  }
  // drop down frame size for higher initial frame rate
  if (config.pixel_format == PIXFORMAT_JPEG) {
    s->set_framesize(s, FRAMESIZE_QVGA);
  }

#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

#if defined(CAMERA_MODEL_ESP32S3_EYE)
  s->set_vflip(s, 1);
#endif

// Setup LED FLash if LED pin is defined in camera_pins.h
#if defined(LED_GPIO_NUM)
  setupLedFlash();
#endif
  Serial.println("===================camera init end===================");

  Serial.println("===================wifi start===================");
  WiFi.begin(ssid, password);
  WiFi.setSleep(false);

  Serial.print("WiFi connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("===================wifi end===================");


  startCameraServer();
  Serial.print("Camera Ready! Use 'http://");
  Serial.print(WiFi.localIP());
  Serial.println("' to connect");

  // Serial.println("===================setupMotor start===================");
  // pinMode(MOTOR_IN1, OUTPUT);
  // pinMode(MOTOR_IN2, OUTPUT);
  // analogWrite(ENA, SPEED);  // 速度控制（0-255）

  // pinMode(MOTOR_IN11, OUTPUT);
  // pinMode(MOTOR_IN22, OUTPUT);
  // analogWrite(ENAA, SPEED);  // 速度控制（0-255）

  // digitalWrite(MOTOR_IN1, LOW);
  // digitalWrite(MOTOR_IN2, LOW);
  // analogWrite(ENA, 0);
  // digitalWrite(MOTOR_IN11, LOW);
  // digitalWrite(MOTOR_IN22, LOW);
  // analogWrite(ENAA, 0);

  // Serial.println("Motor pins initialized");
  // Serial.println("===================setupMotor start===================");
}

void loop() {
  // Monitor system health
  static unsigned long lastCheck = millis();
  if (millis() - lastCheck > 30000) {
    lastCheck = millis();

    // Check free memory
    uint32_t freeHeap = ESP.getFreeHeap();
    Serial.printf("Free memory: %d bytes\n", freeHeap);

    // Check WiFi connection
    if (WiFi.status() != WL_CONNECTED) {
      Serial.println("WiFi disconnected! Attempting to reconnect...");
      WiFi.reconnect();
    }

    // Restart if memory gets too low
    if (freeHeap < 15000) {
      Serial.println("Low memory detected, restarting...");
      delay(1000);
      ESP.restart();
    }
  }

  delay(10000);
}

// 初始化电机控制引脚
void setupMotor() {
  Serial.println("===================setupMotor start===================");

  pinMode(MOTOR_IN1, OUTPUT);
  pinMode(MOTOR_IN2, OUTPUT);
  analogWrite(ENA, SPEED);  // 速度控制（0-255）

  pinMode(MOTOR_IN11, OUTPUT);
  pinMode(MOTOR_IN22, OUTPUT);
  analogWrite(ENAA, SPEED);  // 速度控制（0-255）

  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, LOW);
  analogWrite(ENA, 0);
  digitalWrite(MOTOR_IN11, LOW);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, 0);
  Serial.println("Motor pins initialized");

  Serial.println("===================setupMotor start===================");
}
// 电机控制函数
void motorForward() {
  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, HIGH);
  analogWrite(ENA, SPEED);

  digitalWrite(MOTOR_IN11, HIGH);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, SPEED);
  Serial.println("Motor: Forward");
}

void motorLeft() {
  digitalWrite(MOTOR_IN11, HIGH);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, SPEED);

  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, LOW);
  analogWrite(ENA, 0);
  Serial.println("Motor: Left");
}

void motorRight() {
  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, HIGH);
  analogWrite(ENA, SPEED);

  digitalWrite(MOTOR_IN11, LOW);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, 0);
  Serial.println("Motor: Right");
}

void motorBackward() {
  digitalWrite(MOTOR_IN1, HIGH);
  digitalWrite(MOTOR_IN2, LOW);
  analogWrite(ENA, SPEED);

  digitalWrite(MOTOR_IN11, LOW);
  digitalWrite(MOTOR_IN22, HIGH);
  analogWrite(ENAA, SPEED);
  Serial.println("Motor: Backward");
}

void motorStop() {
  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, LOW);
  analogWrite(ENA, 0);

  digitalWrite(MOTOR_IN11, LOW);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, 0);
  Serial.println("Motor: Stop");
}

// 电机控制处理函数
static esp_err_t control_handler(httpd_req_t *req) {
  char buf[100];
  int ret, remaining = req->content_len;

  // 读取请求数据
  int total_received = 0;
  while (remaining > 0) {
    ret = httpd_req_recv(req, buf + total_received, min(remaining, (int)sizeof(buf) - total_received - 1));
    if (ret <= 0) {
      return ESP_FAIL;
    }
    total_received += ret;
    remaining -= ret;
  }
  buf[total_received] = '\0';

  // 解析控制命令
  String command = String(buf);
  Serial.println("Received command: " + command);

  if (command == "forward") {
    motorForward();
  } else if (command == "left") {
    motorLeft();
  } else if (command == "right") {
    motorRight();
  } else if (command == "backward") {
    motorBackward();
  } else if (command == "stop") {
    motorStop();
  }

  httpd_resp_set_type(req, "text/plain");
  return httpd_resp_send(req, "OK", 2);
}



static esp_err_t index_handler(httpd_req_t *req) {
  const char *html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <title>ESP32-CAM 单电机控制</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" charset="UTF-8">
    <style>
        body { 
            margin: 0; 
            padding: 20px; 
            background: #2c3e50; 
            color: white; 
            font-family: Arial, sans-serif; 
        }
        .container { 
            max-width: 1200px; 
            margin: 0 auto; 
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
            justify-content: center;
        }
        .control-panel { 
            background: #34495e; 
            padding: 20px; 
            border-radius: 10px; 
            flex: 1;
            min-width: 300px;
            max-width: 500px;
        }
        .video-container { 
            background: #34495e; 
            padding: 15px; 
            border-radius: 10px; 
            flex: 1;
            min-width: 300px;
            max-width: 500px;
            display: flex;
            flex-direction: column;
            align-items: center;
            position: relative;
            overflow: visible; /* 确保旋转内容不会溢出 */
        }
        .video-wrapper {
            position: relative;
            width: 100%;
            max-width: 320px;
            margin: 20px 0;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 240px; /* 确保有足够空间容纳旋转后的视频 */
            transition: all 0.3s ease; /* 添加过渡效果 */
        }
        .video-wrapper.original-size {
            max-width: 800px;
            min-height: 600px;
        }
        .btn { 
            padding: 15px 25px; 
            margin: 10px; 
            border: none; 
            border-radius: 8px; 
            font-size: 16px; 
            cursor: pointer; 
        }
        .forward { background: #27ae60; color: white; }
        .backward { background: #e74c3c; color: white; }
        .stop { background: #7f8c8d; color: white; }
        .left { background: #f39c12; color: white; }
        .right { background: #3498db; color: white; }
        .rotate { background: #9b59b6; color: white; }
        .stream-toggle { background: #1abc9c; color: white; }
        .reset-rotate { background: #e67e22; color: white; }
        .original-size-btn { background: #8e44ad; color: white; }
        .video-stream { 
            width: 100%; 
            max-width: 320px; 
            border: 2px solid #7f8c8d; 
            border-radius: 5px; 
            transition: all 0.3s ease;
            display: block;
        }
        .video-stream.original-size {
            max-width: 800px;
            width: 800px;
            height: 600px;
        }
        .status { 
            margin-top: 10px; 
            color: #bdc3c7; 
            text-align: center;
        }
        
        /* 环状控制球样式 */
        .joystick-container {
            display: flex;
            justify-content: center;
            margin: 20px 0;
        }
        .joystick {
            position: relative;
            width: 200px;
            height: 200px;
            background: #34495e;
            border-radius: 50%;
            border: 3px solid #7f8c8d;
            box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
        }
        .joystick-center {
            position: absolute;
            width: 60px;
            height: 60px;
            background: #e74c3c;
            border-radius: 50%;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            transition: transform 0.1s ease;
            box-shadow: 0 0 10px rgba(0,0,0,0.3);
        }
        .joystick.active .joystick-center {
            background: #c0392b;
        }
        
        /* 按钮布局优化 */
        .button-row {
            display: flex;
            justify-content: center;
            flex-wrap: wrap;
        }
        .button-row .btn {
            flex: 1;
            min-width: 120px;
            max-width: 150px;
        }
        .direction-controls {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-bottom: 15px;
        }
        
        /* 视频控制按钮样式 */
        .video-controls {
            display: flex;
            justify-content: center;
            margin-top: 20px;
            flex-wrap: wrap;
            position: relative;
            z-index: 10; /* 确保按钮在旋转内容之上 */
            transition: margin-top 0.3s ease; /* 添加过渡效果 */
        }
        .video-controls.original-size {
            margin-top: 40px; /* 当视频变大时，按钮下移 */
        }
        .video-controls .btn {
            padding: 10px 20px;
            font-size: 14px;
            margin: 5px;
        }
        
        /* 响应式设计 */
        @media (max-width: 768px) {
            .container {
                flex-direction: column;
            }
            .control-panel, .video-container {
                max-width: 100%;
            }
            .video-wrapper {
                min-height: 200px;
            }
            .video-wrapper.original-size {
                max-width: 100%;
                min-height: 400px;
            }
            .video-stream.original-size {
                max-width: 100%;
                width: 100%;
                height: auto;
            }
        }
        
        h2 {
            text-align: center;
            margin-top: 0;
            position: relative;
            z-index: 10; /* 确保标题在旋转内容之上 */
        }
        
        .video-status-area {
            position: relative;
            z-index: 10; /* 确保状态信息在旋转内容之上 */
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <!-- 左侧：控制面板 -->
        <div class="control-panel">
            <h2>电机控制面板</h2>
            
            <!-- 方向控制按钮 -->
            <div class="direction-controls">
                <div>
                    <button class="btn forward" onmousedown="controlMotor('forward')" ontouchstart="controlMotor('forward')" onmouseup="controlMotor('stop')" ontouchend="controlMotor('stop')">前进</button>
                </div>
                <div class="button-row">
                    <button class="btn left" onmousedown="controlMotor('left')" ontouchstart="controlMotor('left')" onmouseup="controlMotor('stop')" ontouchend="controlMotor('stop')">左转</button>
                    <button class="btn stop" onclick="controlMotor('stop')">停止</button>
                    <button class="btn right" onmousedown="controlMotor('right')" ontouchstart="controlMotor('right')" onmouseup="controlMotor('stop')" ontouchend="controlMotor('stop')">右转</button>
                </div>
                <div>
                    <button class="btn backward" onmousedown="controlMotor('backward')" ontouchstart="controlMotor('backward')" onmouseup="controlMotor('stop')" ontouchend="controlMotor('stop')">后退</button>
                </div>
            </div>
            
            <!-- 环状控制球 -->
            <div class="joystick-container">
                <div class="joystick" id="joystick">
                    <div class="joystick-center"></div>
                </div>
            </div>
            
            <div id="status" class="status">就绪</div>
        </div>

        <!-- 右侧：视频面板 -->
        <div class="video-container">
            <h2>视频监控</h2>
            <div class="video-wrapper" id="videoWrapper">
                <img src="http://192.168.1.49:81/stream" class="video-stream" id="videoStream"
                     onload="document.getElementById('videoStatus').textContent='视频流连接正常'"
                     onerror="document.getElementById('videoStatus').textContent='视频流连接失败'">
            </div>
            <div class="video-controls" id="videoControls">
                <!-- 旋转按钮 -->
                <button class="btn rotate" onclick="rotateVideo(10)">顺时针旋转10°</button>
                <button class="btn rotate" onclick="rotateVideo(-10)">逆时针旋转10°</button>
                <!-- 重置旋转按钮 -->
                <button class="btn reset-rotate" onclick="resetRotation()">重置旋转</button>
                <!-- 视频流控制按钮 -->
                <button class="btn stream-toggle" id="streamToggle" onclick="toggleVideoStream()">关闭视频流</button>
                <!-- 新增：显示原图按钮 -->
                <button class="btn original-size-btn" id="originalSizeToggle" onclick="toggleOriginalSize()">显示原图</button>
            </div>
            <div class="video-status-area">
                <div id="videoStatus" class="status">正在连接视频流...</div>
                <div id="rotationStatus" class="status">旋转角度: 0°</div>
            </div>
        </div>
    </div>

    <script>
        // 新增：视频旋转相关变量
        let currentRotation = 0;
        
        // 新增：视频流控制变量
        let isStreamActive = true;
        const videoStream = document.getElementById('videoStream');
        const streamToggle = document.getElementById('streamToggle');
        const originalStreamSrc = videoStream.src;
        
        // 新增：原图显示控制变量
        let isOriginalSize = false;
        const originalSizeToggle = document.getElementById('originalSizeToggle');
        const videoWrapper = document.getElementById('videoWrapper');
        const videoControls = document.getElementById('videoControls');
        
        // 修改：旋转视频函数，支持任意角度
        function rotateVideo(angle) {
            currentRotation = (currentRotation + angle) % 360;
            // 确保角度在0-359之间
            if (currentRotation < 0) {
                currentRotation += 360;
            }
            videoStream.style.transform = `rotate(${currentRotation}deg)`;
            updateRotationStatus();
        }

        // 新增：重置旋转函数
        function resetRotation() {
            currentRotation = 0;
            videoStream.style.transform = `rotate(${currentRotation}deg)`;
            updateRotationStatus();
        }

        // 新增：更新旋转状态显示
        function updateRotationStatus() {
            document.getElementById('rotationStatus').textContent = `旋转角度: ${currentRotation}°`;
        }

        // 新增：切换视频流函数
        function toggleVideoStream() {
            if (isStreamActive) {
                // 关闭视频流
                videoStream.src = "";
                streamToggle.textContent = "开启视频流";
                document.getElementById('videoStatus').textContent = "视频流已关闭";
                isStreamActive = false;
            } else {
                // 开启视频流
                videoStream.src = originalStreamSrc + '?t=' + Date.now();
                streamToggle.textContent = "关闭视频流";
                document.getElementById('videoStatus').textContent = "正在连接视频流...";
                isStreamActive = true;
            }
        }

        // 新增：切换原图显示函数
        function toggleOriginalSize() {
            if (isOriginalSize) {
                // 恢复默认大小
                videoWrapper.classList.remove('original-size');
                videoStream.classList.remove('original-size');
                videoControls.classList.remove('original-size');
                originalSizeToggle.textContent = "显示原图";
                isOriginalSize = false;
            } else {
                // 显示原图大小
                videoWrapper.classList.add('original-size');
                videoStream.classList.add('original-size');
                videoControls.classList.add('original-size');
                originalSizeToggle.textContent = "恢复默认";
                isOriginalSize = true;
            }
        }

        function controlMotor(direction) {
            const status = document.getElementById('status');
            status.textContent = '控制: ' + direction;
            
            fetch('/control', {
                method: 'POST',
                body: direction,
                headers: {
                    'Content-Type': 'text/plain'
                }
            })
            .then(response => response.text())
            .then(data => {
                console.log('控制成功:', direction);
            })
            .catch(error => {
                status.textContent = '控制失败';
                console.error('错误:', error);
            });
        }

        // 环状控制球逻辑
        const joystick = document.getElementById('joystick');
        const joystickCenter = joystick.querySelector('.joystick-center');
        let isDragging = false;
        let lastDirection = '';

        // 鼠标事件
        joystick.addEventListener('mousedown', startDrag);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', stopDrag);
        
        // 触摸事件
        joystick.addEventListener('touchstart', function(e) {
            e.preventDefault();
            startDrag(e.touches[0]);
        });
        document.addEventListener('touchmove', function(e) {
            e.preventDefault();
            drag(e.touches[0]);
        });
        document.addEventListener('touchend', stopDrag);
        
        function startDrag(e) {
            isDragging = true;
            joystick.classList.add('active');
            updateJoystick(e);
        }
        
        function drag(e) {
            if (!isDragging) return;
            updateJoystick(e);
        }
        
        function stopDrag() {
            if (!isDragging) return;
            isDragging = false;
            joystick.classList.remove('active');
            joystickCenter.style.transform = 'translate(-50%, -50%)';
            
            // 停止电机
            if (lastDirection) {
                controlMotor('stop');
                lastDirection = '';
            }
        }
        
        function updateJoystick(e) {
            const rect = joystick.getBoundingClientRect();
            const centerX = rect.left + rect.width / 2;
            const centerY = rect.top + rect.height / 2;
            const deltaX = e.clientX - centerX;
            const deltaY = e.clientY - centerY;
            
            // 计算距离和角度
            const distance = Math.min(Math.sqrt(deltaX * deltaX + deltaY * deltaY), rect.width / 2);
            const angle = Math.atan2(deltaY, deltaX);
            
            // 更新控制球位置
            const maxDistance = rect.width / 2 - joystickCenter.offsetWidth / 2;
            const limitedDistance = Math.min(distance, maxDistance);
            const posX = limitedDistance * Math.cos(angle);
            const posY = limitedDistance * Math.sin(angle);
            
            joystickCenter.style.transform = `translate(${posX}px, ${posY}px)`;
            
            // 确定方向
            let direction = '';
            const angleDeg = angle * 180 / Math.PI;
            
            if (distance > 30) { // 最小有效距离
                if (angleDeg >= -45 && angleDeg < 45) {
                    direction = 'right';
                } else if (angleDeg >= 45 && angleDeg < 135) {
                    direction = 'backward';
                } else if (angleDeg >= -135 && angleDeg < -45) {
                    direction = 'forward';
                } else {
                    direction = 'left';
                }
                
                // 只有当方向改变时才发送命令
                if (direction !== lastDirection) {
                    controlMotor(direction);
                    lastDirection = direction;
                }
            }
        }

        // 自动重连视频流（仅在视频流开启时）
        setInterval(() => {
            if (isStreamActive) {
                const img = document.querySelector('.video-stream');
                if (img && img.complete && img.naturalHeight === 0) {
                    img.src = img.src.split('?')[0] + '?t=' + Date.now();
                }
            }
        }, 5000);
    </script>
</body>
</html>
)rawliteral";

  httpd_resp_set_type(req, "text/html");
  return httpd_resp_send(req, html, strlen(html));
}




// ===================== HTTP服务器处理函数 =====================
// MJPEG推流常量定义（修正实际帧率）
#define _STREAM_CONTENT_TYPE "multipart/x-mixed-replace;boundary=frame"
#define _STREAM_BOUNDARY "--frame"
#define _STREAM_PART "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %ld.%06ld\r\n\r\n"
#define CAMERA_FB_TIMEOUT_MS 100            // 相机帧获取超时时间（100ms）
#define TARGET_FPS 10                       // 目标推流帧率（OV3660实际支持的帧率）
#define FRAME_DELAY_MS (1000 / TARGET_FPS)  // 帧间隔延迟

// 全局变量：平均帧率统计（可选，量产可移除）
static uint32_t avg_frame_time = 0;

static esp_err_t stream_handler(httpd_req_t *req) {
  camera_fb_t *fb = NULL;
  struct timeval _timestamp;
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t *_jpg_buf = NULL;
  char part_buf[128];  // 修复：改为字符数组，避免栈溢出

  static int64_t last_frame = 0;
  if (!last_frame) {
    last_frame = esp_timer_get_time();
  }

  // 设置响应头：修正实际帧率
  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if (res != ESP_OK) {
    log_e("Set content type failed");
    return res;
  }

  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  httpd_resp_set_hdr(req, "X-Framerate", "10");  // 改为实际10fps

  // 推流主循环
  while (true) {
    // 1. 获取相机帧：添加超时保护，避免永久阻塞
    fb = esp_camera_fb_get();
    if (!fb) {
      log_e("Camera capture failed (timeout)");
      res = ESP_FAIL;
      break;
    }

    // 2. 处理JPEG帧（原生JPEG或格式转换）
    _timestamp.tv_sec = fb->timestamp.tv_sec;
    _timestamp.tv_usec = fb->timestamp.tv_usec;
    if (fb->format != PIXFORMAT_JPEG) {
      // JPEG转换：设置质量为30（0-100，数值越大，质量越好，平衡体积和画质，512KB SRAM优化）
      bool jpeg_converted = frame2jpg(fb, 30, &_jpg_buf, &_jpg_buf_len);
      esp_camera_fb_return(fb);  // 立即释放相机帧缓冲区
      fb = NULL;
      if (!jpeg_converted) {
        log_e("JPEG compression failed");
        res = ESP_FAIL;
        break;
      }
    } else {
      _jpg_buf_len = fb->len;
      _jpg_buf = fb->buf;
    }

    // 3. 发送MJPEG分块数据
    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if (res == ESP_OK) {
      size_t hlen = snprintf(part_buf, sizeof(part_buf), _STREAM_PART,
                             (unsigned int)_jpg_buf_len,
                             (long)_timestamp.tv_sec,
                             (long)_timestamp.tv_usec);
      // 检查格式化是否溢出
      if (hlen >= sizeof(part_buf)) {
        log_e("Part buf overflow");
        res = ESP_FAIL;
        break;
      }
      res = httpd_resp_send_chunk(req, part_buf, hlen);
    }
    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }

    // 4. 释放资源：区分原生JPEG和转换后的JPEG
    if (fb) {
      esp_camera_fb_return(fb);
      fb = NULL;
      _jpg_buf = NULL;
    } else if (_jpg_buf) {
      free(_jpg_buf);
      _jpg_buf = NULL;
      _jpg_buf_len = 0;
    }

    // 5. 检查发送结果，中断循环
    if (res != ESP_OK) {
      log_e("Send frame failed");
      break;
    }

    // 6. 帧率控制：添加延迟，降低CPU占用
    int64_t fr_end = esp_timer_get_time();
    int64_t frame_time = fr_end - last_frame;
    last_frame = fr_end;
    frame_time /= 1000;  // 转换为ms

    // 计算平均帧率（量产可移除，减少CPU开销）
    avg_frame_time = (avg_frame_time * 9 + (uint32_t)frame_time) / 10;

    // 补全延迟：若帧处理时间小于目标间隔，休眠剩余时间
    if (frame_time < FRAME_DELAY_MS) {
      vTaskDelay((FRAME_DELAY_MS - frame_time) / portTICK_PERIOD_MS);
    }
  }

  // 7. 统一资源清理：避免内存泄漏（关键！）
  if (fb) {
    esp_camera_fb_return(fb);
  }
  if (_jpg_buf) {
    free(_jpg_buf);
  }

  // 8. 发送结束分块，关闭连接
  httpd_resp_send_chunk(req, NULL, 0);

  return res;
}

void startCameraServer() {
  Serial.println("========startCameraServer 开始==========");

  httpd_handle_t stream_httpd = NULL;
  httpd_handle_t camera_httpd = NULL;

  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  config.max_uri_handlers = 2;
  config.stack_size = 4096;
  config.max_open_sockets = 2;
  config.recv_wait_timeout = 5;
  config.send_wait_timeout = 5;

  httpd_uri_t index_uri = {
    .uri = "/",
    .method = HTTP_GET,
    .handler = index_handler,
    .user_ctx = NULL
  };

  httpd_uri_t stream_uri = {
    .uri = "/stream",
    .method = HTTP_GET,
    .handler = stream_handler,
    .user_ctx = NULL
  };

  httpd_uri_t control_uri = {
    .uri = "/control",
    .method = HTTP_POST,
    .handler = control_handler,
    .user_ctx = NULL
  };
  if (httpd_start(&camera_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(camera_httpd, &index_uri);
    httpd_register_uri_handler(camera_httpd, &control_uri);
    Serial.println("camera_httpd服务器启动成功");
  } else {
    Serial.println("camera_httpd服务器启动失败");
  }

  config.server_port += 1;
  config.ctrl_port += 1;
  config.max_uri_handlers = 1;

  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &stream_uri);
    Serial.println("stream_httpd服务器启动成功");
  } else {
    Serial.println("stream_httpd服务器启动失败");
  }

  Serial.println("========startCameraServer 结束==========");
}
